/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package bd.org.apache.commons.math.analysis;

import bd.org.apache.commons.math.analysis.function.Identity;
import bd.org.apache.commons.math.exception.NotStrictlyPositiveException;
import bd.org.apache.commons.math.exception.NumberIsTooLargeException;
import bd.org.apache.commons.math.exception.util.LocalizedFormats;

/**
 * Utilities for manipulating function objects.
 *
 * @version $Id$
 * @since 3.0
 */
public class FunctionUtils {

    /**
     * Class only contains static methods.
     */
    private FunctionUtils() {}

    /**
     * Compose functions.  The functions in the argument list are composed
     * sequentially, in the order given.  For example, compose(f1,f2,f3)
     * acts like f1(f2(f3(x))).
     *
     * @param f List of functions.
     * @return the composite function.
     */
    public static UnivariateFunction compose(final UnivariateFunction... f) {

        return new UnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                double r = x;

                for (int i = f.length - 1; i >= 0; i--) {

                    r = f[i].value(r);
                }

                return r;
            }
        };
    }


    /**
     * Compose functions.  The functions in the argument list are composed
     * sequentially, in the order given.  For example, compose(f1,f2,f3)
     * acts like f1(f2(f3(x))).
     *
     * @param f List of functions.
     * @return the composite function.
     */
    public static DifferentiableUnivariateFunction compose(
            final DifferentiableUnivariateFunction... f) {

        return new DifferentiableUnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                double r = x;

                for (int i = f.length - 1; i >= 0; i--) {

                    r = f[i].value(r);
                }

                return r;
            }

            /** {@inheritDoc} */
            @Override
            public UnivariateFunction derivative() {

                return new UnivariateFunction() {

                    /** {@inheritDoc} */
                    @Override
                    public double value(double x) {

                        double p = 1;
                        double r = x;

                        for (int i = f.length - 1; i >= 0; i--) {

                            p *= f[i].derivative().value(r);
                            r = f[i].value(r);
                        }

                        return p;
                    }
                };
            }
        };
    }


    /**
     * Add functions.
     *
     * @param f List of functions.
     * @return a function that computes the sum of the functions.
     */
    public static UnivariateFunction add(final UnivariateFunction... f) {

        return new UnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                double r = f[0].value(x);

                for (int i = 1; i < f.length; i++) {

                    r += f[i].value(x);
                }

                return r;
            }
        };
    }


    /**
     * Add functions.
     *
     * @param f List of functions.
     * @return a function that computes the sum of the functions.
     */
    public static DifferentiableUnivariateFunction add(
            final DifferentiableUnivariateFunction... f) {

        return new DifferentiableUnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                double r = f[0].value(x);

                for (int i = 1; i < f.length; i++) {

                    r += f[i].value(x);
                }

                return r;
            }

            /** {@inheritDoc} */
            @Override
            public UnivariateFunction derivative() {

                return new UnivariateFunction() {

                    /** {@inheritDoc} */
                    @Override
                    public double value(double x) {

                        double r = f[0].derivative().value(x);

                        for (int i = 1; i < f.length; i++) {

                            r += f[i].derivative().value(x);
                        }

                        return r;
                    }
                };
            }
        };
    }


    /**
     * Multiply functions.
     *
     * @param f List of functions.
     * @return a function that computes the product of the functions.
     */
    public static UnivariateFunction multiply(final UnivariateFunction... f) {

        return new UnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                double r = f[0].value(x);

                for (int i = 1; i < f.length; i++) {

                    r *= f[i].value(x);
                }

                return r;
            }
        };
    }


    /**
     * Multiply functions.
     *
     * @param f List of functions.
     * @return a function that computes the product of the functions.
     */
    public static DifferentiableUnivariateFunction multiply(
            final DifferentiableUnivariateFunction... f) {

        return new DifferentiableUnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                double r = f[0].value(x);

                for (int i = 1; i < f.length; i++) {

                    r *= f[i].value(x);
                }

                return r;
            }

            /** {@inheritDoc} */
            @Override
            public UnivariateFunction derivative() {

                return new UnivariateFunction() {

                    /** {@inheritDoc} */
                    @Override
                    public double value(double x) {

                        double sum = 0;

                        for (int i = 0; i < f.length; i++) {

                            double prod = f[i].derivative().value(x);

                            for (int j = 0; j < f.length; j++) {

                                if (i != j) {
                                    prod *= f[j].value(x);
                                }
                            }

                            sum += prod;
                        }

                        return sum;
                    }
                };
            }
        };
    }


    /**
     * Returns the univariate function <br/>
     * {@code h(x) = combiner(f(x), g(x))}.
     *
     * @param combiner Combiner function.
     * @param f Function.
     * @param g Function.
     * @return the composite function.
     */
    public static UnivariateFunction combine(final BivariateFunction combiner,
            final UnivariateFunction f, final UnivariateFunction g) {

        return new UnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                return combiner.value(f.value(x), g.value(x));
            }
        };
    }


    /**
     * Returns a MultivariateFunction h(x[]) defined by <pre> <code>
     * h(x[]) = combiner(...combiner(combiner(initialValue,f(x[0])),f(x[1]))...),f(x[x.length-1]))
     * </code></pre>
     *
     * @param combiner Combiner function.
     * @param f Function.
     * @param initialValue Initial value.
     * @return a collector function.
     */
    public static MultivariateFunction collector(final BivariateFunction combiner,
            final UnivariateFunction f, final double initialValue) {

        return new MultivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double[] point) {

                double result = combiner.value(initialValue, f.value(point[0]));

                for (int i = 1; i < point.length; i++) {

                    result = combiner.value(result, f.value(point[i]));
                }

                return result;
            }
        };
    }


    /**
     * Returns a MultivariateFunction h(x[]) defined by <pre> <code>
     * h(x[]) = combiner(...combiner(combiner(initialValue,x[0]),x[1])...),x[x.length-1])
     * </code></pre>
     *
     * @param combiner Combiner function.
     * @param initialValue Initial value.
     * @return a collector function.
     */
    public static MultivariateFunction collector(final BivariateFunction combiner,
            final double initialValue) {

        return collector(combiner, new Identity(), initialValue);
    }


    /**
     * Create a unary function by fixing the first argument of a binary function.
     *
     * @param f Binary function.
     * @param fixed Value to which the first argument of {@code f} is set.
     * @return the unary function h(x) = f(fixed, x)
     */
    public static UnivariateFunction fix1stArgument(final BivariateFunction f, final double fixed) {

        return new UnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                return f.value(fixed, x);
            }
        };
    }


    /**
     * Create a unary function by fixing the second argument of a binary function.
     *
     * @param f Binary function.
     * @param fixed Value to which the second argument of {@code f} is set.
     * @return the unary function h(x) = f(x, fixed)
     */
    public static UnivariateFunction fix2ndArgument(final BivariateFunction f, final double fixed) {

        return new UnivariateFunction() {

            /** {@inheritDoc} */
            @Override
            public double value(double x) {

                return f.value(x, fixed);
            }
        };
    }


    /**
     * <p>
     * Samples the specified univariate real function on the specified interval.
     * </p>
     * <p>
     * The interval is divided equally into {@code n} sections and sample points
     * are taken from {@code min} to {@code max - (max - min) / n}; therefore
     * {@code f} is not sampled at the upper bound {@code max}.
     * </p>
     *
     * @param f the function to be sampled
     * @param min the (inclusive) lower bound of the interval
     * @param max the (exclusive) upper bound of the interval
     * @param n the number of sample points
     * @return the array of samples
     * greater than, or equal to the upper bound {@code max}
     * {@code n} is negative
     */
    public static double[] sample(UnivariateFunction f, double min, double max, int n) {

        if (n <= 0) {

            throw new NotStrictlyPositiveException(LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES,
                    Integer.valueOf(n));
        }

        if (min >= max) {
            throw new NumberIsTooLargeException(min, max, false);
        }

        double[] s = new double[n];
        double   h = (max - min) / n;

        for (int i = 0; i < n; i++) {

            s[i] = f.value(min + i * h);
        }

        return s;
    }
}
